#Contains all of the code for the graph
#Subclassed from a matplotlib qt widget
#
#Written by Nathan

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as MPL_QT
from matplotlib.figure import Figure as MPL_FIGURE
from matplotlib import dates as MPL_DATES
from matplotlib.ticker import MaxNLocator as MPL_SETTICKS
import datetime

class MPLCanvas(MPL_QT):
    def __init__(self, parent=None, width=2, height=2, dpi=100):
        fig = MPL_FIGURE(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        
        self.colors = ["r", "g", "b", "m", "y", "c"]
        self.dashes = ["-", ":", "--"]
        self.markers = ["o", "+", "v", "s", "*", "x", "D"]
        
        self.started = False
            
        MPL_QT.__init__(self, fig)
        self.setParent(parent)        
        MPL_QT.setSizePolicy(self,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
        MPL_QT.setMinimumSize(self, 400, 400)
        MPL_QT.updateGeometry(self)

# subclass for graph        
class DynamicCanvas(MPLCanvas):
    def __init__(self, *args, **kwargs):
        MPLCanvas.__init__(self, *args, **kwargs)
        
        self.cidrelease = self.mpl_connect('button_release_event', self.on_release)

    
    def compute_initial_figure(self, in_xydata, use_markers=True):
        """Takes list of update objects as specified in GUI controller.
        For each update object, x-data should be POSIX timestamps,
        and y-data values at said times.
        Turns x-data into Python datetime objects.
        Each xy entry is given a unique color/dash/marker combination.
        Resets the x-minimum.
        After figure is drawn, marks graph as initialized so future calls
        are instead made to update_figure().
        """
        self.axes.cla() #clear axes
        self.theLines = [] #clear list of lines
        self.axes.hold(True) #set axis to retain previous lines with each plot() call
        self.xmin = -1.0 #reset x-minimum
        self.figure.autofmt_xdate() #set figure to automatically format the xtick labels
        #self.figure.tight_layout(pad=2)
        
        minutes = MPL_DATES.MinuteLocator()
        minsFmt = MPL_DATES.DateFormatter('%H:%M')
        self.axes.xaxis.set_major_locator(minutes)
        self.axes.xaxis.set_major_formatter(minsFmt)
        
        color_i = 0
        dash_i = [0]*len(self.colors)
        for entry in in_xydata:
            q = color_i % len(self.colors)
            w = dash_i[q] % len(self.dashes)
            dash_i[q] += 1
            e = color_i % len(self.colors)
            color_i += 1
            
            l_col = self.colors[q]
            l_dash = self.dashes[w]
            l_mark = self.markers[e]   
            
            dates = [datetime.datetime.fromtimestamp(float(unixt))for unixt in entry.x]
            if use_markers==False: l_mark = ","
            self.axes.plot_date(dates, map(lambda x: x / 1024,entry.y), linestyle=l_dash, color=l_col, marker=l_mark)

        self.theLines = self.axes.get_lines()        

        self.autodraw()
        
        self.started = True
        
    def update_figure(self, in_xydata):
        """Takes list of update objects as specified in GUI controller.
        For each update object, x-data should be POSIX timestamps,
        and y-data values at said times.
        Turns x-data into Python datetime objects, slices off the left side
        according to the current x-minimum, then sets the line data to the
        sliced lists. If the lengths of the x,y lists are somehow mismatched
        then the longer list is pruned to the length of the shorter.
        """
        
    
        
        i=0
        for entry in in_xydata:        
            dates = [ datetime.datetime.fromtimestamp(float(unixt)) for unixt in entry.x ]
            vals = [(y/1024) for y in entry.y] if (entry.rate) else entry.y
            xmin_index = 0
            
            if self.xmin != -1:
                try: xmin_index = next(x[0] for x in enumerate(dates) if MPL_DATES.date2num(x[1]) >= self.xmin)
                except StopIteration: pass
            
            if xmin_index > 0:
                dates = dates[xmin_index:]
                vals = vals[xmin_index:]             
            
            if len(dates) != len(vals):
                a=len(dates)
                b=len(vals)
                if a>b: dates = dates[:b]
                elif b>a: vals = vals[:a]
            
            self.theLines[i].set_data(dates,vals)
            i+=1
        
        self.autodraw()
                
    def set_xlabel(self, in_label):
        self.axes.set_xlabel(in_label)
        
    def set_ylabel(self, in_label):
        self.axes.set_ylabel(in_label)
    
    def on_release(self, in_event):
        """Callback function for when mouse button released.
        If cursor not within axes, do nothing.
        If left mouse clicked set x-position as the graph's left boundary.
        If right mouse clicked reset graph's left boundary.
        """
        if in_event.inaxes != self.axes: return
        
        if in_event.button == 1:
            self.xmin = in_event.xdata
            return
        
        if in_event.button == 3:
            self.xmin = -1
            return
        
    def set_ticks(self):
        res = self.get_width_height()
        num_xticks = res[0]/80
        num_yticks = res[1]/60       
        self.axes.xaxis.set_major_locator(MPL_SETTICKS(num_xticks))
        self.axes.yaxis.set_major_locator(MPL_SETTICKS(num_yticks))
        
    def autodraw(self):
        """Bundles up methods necessary to correctly draw plot.
        relim() -- sets current x,y data limits from the lines' data
        autoscale_view() -- scales plot according to current data limits
        set_ticks -- scales number of ticks according to figure resolution
        draw() -- redraws QT figure
        """
        self.axes.relim()
        self.axes.autoscale_view()
        self.set_ticks()
        self.draw()
        
        
        